/**
 * Main game logic, it also communicates to the controller
 * @author Long & Todd
 */
package com.dudo.model;

import com.dudo.*;
import java.util.ArrayList;
import java.util.Random;


public class GameManager {
	// Singleton
	private static GameManager gameManager;
	DudoGame controller;
	public static final int NO_WINNER = -1, INITIAL_NUM_DICE = 5, HUMAN = 0, WATSON = 1;
	public static final String[] names = {"YOU", "WATSON"};
	private static Random random = new Random();
	private ArrayList<DudoPlayer> players = new ArrayList<DudoPlayer>();
	int numPlayers;
	int currentPlayerNum;
	int[] playerNumDice; 
	ArrayList<Claim> claimList = new ArrayList<Claim>();
	ArrayList<Integer> claimPlayerList = new ArrayList<Integer>();
	int[][] allRolls;
	int totalDice = 0;
	ArrayList<Claim> claims;
	
	public String toString() { // report current status of the game
		String s = "";
		s += "Your dice: \n";
		s += players.get(0).toString() + "\n";
		s += "WATSON's dice: \n";
		for (int i = 0; i < playerNumDice[WATSON]; ++i)
			s += "X\t";
		s += "\n";
		return s;
	}

	private GameManager(DudoGame game) {
		controller = game; // Bind to the controller
		
		addPlayer(new HumanDudoPlayer());
		addPlayer(new SimpleDudoPlayer());
		
		// Initialize
		numPlayers = players.size();
		currentPlayerNum = 0; // Player 0 starts first round. Successive rounds start with dudo challenge winner.
		// Start each player with INITIAL_NUM_DICE dice each
		playerNumDice = new int[numPlayers];
		for (int i = 0; i < numPlayers; i++)
			playerNumDice[i] = INITIAL_NUM_DICE;
	}
	public static void initialize(DudoGame game) {
		gameManager = new GameManager(game);
	}
	public static GameManager getObject() {
		return gameManager;
	}

	// Receive a claim from current player
	public void receiveClaim(int count, int rk) {
		String message = ""; // Info about current claim to be sent to controller
		
		Claim claim = new Claim(count, rk);
		Claim prevClaim = claimList.isEmpty() ? null : claimList.get(claimList.size() - 1);

		// If claim invalid, interpret as dudo challenge.  If dudo challenge before claim is made, interpret as lowest claim.
		int claimIndex = claims.indexOf(claim);
		if (claimIndex == -1 || claimIndex <= claims.indexOf(prevClaim)) {
			message += String.format("%s made the invalid claim \"%s\".  It will be interpreted as a dudo challenge.\n", names[currentPlayerNum], claim.toString());
			claim = claims.get(claims.size() - 1);
		}					
		if (claim.isDudoClaim() && claimList.isEmpty()) {
			claim = claims.get(0);
			message += String.format("%s made a dudo challenge with no prior claims.  It will be interpreted as claim \"%s\".\n", names[currentPlayerNum], claim.toString());
		}

		Claim previousClaim = claim; 
		int previousPlayerNum = currentPlayerNum;
		// If there is a past claim, we display it
		if (!claimList.isEmpty()) {
			previousPlayerNum = claimPlayerList.get(claimPlayerList.size() - 1);
			previousClaim = claimList.get(claimList.size() - 1);
			message += String.format("%s : %s\n", names[previousPlayerNum], previousClaim.toString());
		}
		message += String.format("%s : %s\n", names[currentPlayerNum], claim.toString());
		
		claimList.add(claim);
		claimPlayerList.add(currentPlayerNum);
		
		for (int i = 0; i < numPlayers; i++) 
			players.get(i).reportClaim(currentPlayerNum, claim);

		// If dudo challenge, 
		if (claim.isDudoClaim()) {
			// Report all rolls
			for (int p = 0; p < numPlayers; p++) { // For each player,
				// Create a copy of allRolls
				int[][] allRollsCopy = new int[numPlayers][];
				for (int i = 0; i < numPlayers; i++) 
					allRollsCopy[i] = allRolls[i].clone();
				players.get(p).reportAllRolls(allRollsCopy);
			}

			// Evaluate challenge
			int challengedNum = previousClaim.getNum();
			int rank = previousClaim.getRank();
			int actualNum = 0;
			for (int i = 0; i < numPlayers; i++) // For each player, ...
				for (int j = 0; j < allRolls[i].length; j++) // ... for each die that player rolled, ...
					if (allRolls[i][j] == 1 || allRolls[i][j] == rank) // .. if that's the challenged rank, ...
						actualNum++; // ... count it.

			if (actualNum < challengedNum) {
				// Dudo challenge successful, so remove previous player dice.
				playerNumDice[previousPlayerNum] -= (challengedNum - actualNum);
				if (playerNumDice[previousPlayerNum] < 0)
					playerNumDice[previousPlayerNum] = 0;
				// Current player (dudo challenger) begins the next round
				
				// Update claim info
				message += "Dudo challenge was sucessful! " + (challengedNum - actualNum) + " die(ce) is removed from " + names[previousPlayerNum]+ ".\n";
			}
			else {
				// Dudo challenge unsuccessful, so remove challenger dice.
				// At least one die must be removed.
				int removedDice = Math.max(1, actualNum - challengedNum); // Number of dice to be removed
				playerNumDice[currentPlayerNum] -= removedDice;
				if (playerNumDice[currentPlayerNum] < 0)
					playerNumDice[currentPlayerNum] = 0;
				// Previous player begins the next round
				currentPlayerNum = previousPlayerNum;
				
				// Update claim info
				message += "Dudo challenge was unsucessful! " + removedDice + " die(ce) is removed from challenger.\n";
			}
			
			message += "Your dice: " + players.get(HUMAN);
			message += "WATSON's dice: " + players.get(WATSON);
		}
		else // Otherwise (no dudo challenge), advance to next player with dice remaining.
			do {
				currentPlayerNum++;
				if (currentPlayerNum == numPlayers)
					currentPlayerNum = 0;
			} while (playerNumDice[currentPlayerNum] == 0);
		
		// Display claim info
		controller.updateCurrentClaim(message);
		
		// If this was a dudo claim, update game
		if (claimList.get(claimList.size() - 1).isDudoClaim()) {
			updateGame();
		} else {
			players.get(currentPlayerNum).getClaim(); // ask the current player to make a claim
			
		}
	}
	
	public void updateGame() {
		// Check whether only one player remains with dice.
		int winner = getWinner();
		if (winner != NO_WINNER) { // Game ends
			// Send a message to controller
			String message = (winner == 0) ? "You won!" : "Sorry, Watson is just a little bit smarter. :)";
			controller.updateGameStatus(message);
			
			// Report the number of dice for each player
			for (DudoPlayer player : players)
				player.reportPlayerDice(playerNumDice.clone());
			
			return;
		}
		
		claimList.clear();
		claimPlayerList.clear();

		// Report the number of dice for each player, this also reset each player's history of claims
		for (DudoPlayer player : players)
			player.reportPlayerDice(playerNumDice.clone());

		// Roll all dice
		totalDice = 0;
		allRolls = new int[numPlayers][];
		for (int i = 0; i < numPlayers; i++) { // For each player, ...
			allRolls[i] = new int[playerNumDice[i]];
			totalDice += playerNumDice[i];
			for (int j = 0; j < allRolls[i].length; j++) // ... for each die that player still has, ...
				allRolls[i][j] = random.nextInt(6) + 1; // ...roll the die.
		}

		// Set individual rolls for each player
		for (int i = 0; i < numPlayers; i++)
			players.get(i).setRoll(allRolls[i].clone());

		// Get valid ordered claims for the round
		claims = Claim.diceClaims.get(totalDice);

		// Send new status to controller
		controller.updateGameStatus(this.toString());
		
		// Ask current player to make a claim
		players.get(currentPlayerNum).getClaim();
	}

	public int getWinner() {
		int playersWithDice = 0;
		int lastPlayerWithDice = -1;
		for (int i = 0; i < playerNumDice.length; i++)
			if (playerNumDice[i] > 0) {
				lastPlayerWithDice = i;
				playersWithDice++;
				if (playersWithDice > 1)
					return -1;
			}
		return lastPlayerWithDice;
	}

	public void addPlayer(DudoPlayer player) {
		player.setPlayer(players.size());
		players.add(player);
	}
}
